#include "cybertron/core/ComputerEnv.hpp"
#include "cybertron/core/Log.hpp"
#include "cybertron/core/UtilString.hpp"

#include <iostream>

#ifdef CYBERTRON_WIN
#include <string>
#include <windows.h>
#include <Iphlpapi.h>
#include <stdio.h>
#include <vector>
#pragma comment(lib,"Iphlpapi.lib")
#pragma comment(lib, "ws2_32")
#else
#include <cstring>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/statfs.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#endif

#include <algorithm>

CYBERTRON_BEGIN

ComputerEnv::ComputerEnv()
{
	Init();
}

ComputerEnv::~ComputerEnv()
{
}

bool ComputerEnv::Init()
{
	if (mbInit)
	{
		return false;
	}

	mbInit = true;

	CreateIPConfigEnv();
	CreateCpuEnv();
	CreateMemoryEnv();
	CreateDiskEnv();
	CreateDisplayEnv();
	return true;
}

std::vector<std::string> ComputerEnv::StringSplit(const  std::string& string, const std::string& splitString)
{
	std::vector<std::string> elements;
	size_t pos = 0;
	size_t length = string.length();
	size_t splitStringLength = splitString.length();
	if (splitStringLength == 0)
		return elements;

	while (pos < length)
	{
		size_t findPos = string.find(splitString, pos);
		if (findPos == std::string::npos)
		{
			elements.push_back(string.substr(pos, length - pos));
			break;
		}
		elements.push_back(string.substr(pos, findPos - pos));
		pos = findPos + splitStringLength;
	}

	for (size_t elementIndex = 0; elementIndex < elements.size(); elementIndex++)
	{
		elements[elementIndex].erase(0, elements[elementIndex].find_first_not_of(" "));
		elements[elementIndex].erase(elements[elementIndex].find_last_not_of(" ") + 1);
	}

	return elements;
}

void ComputerEnv::ParseFile(const std::string& file, std::vector<KeyValue>& informations, std::vector<std::string>& items)
{
	FILE* fileHandle = nullptr;
	std::vector<KeyValue> informationItems;
	fileHandle = fopen(file.c_str(), "r");
	if (fileHandle != nullptr)
	{
		char temp[1000] = { 0 };
		while (!feof(fileHandle))
		{
			memset(temp, 0, sizeof(temp));
			fgets(temp, sizeof(temp) - 1, fileHandle);

			std::string info = temp;
			std::transform(info.begin(), info.end(), info.begin(), ::toupper);

			std::vector<std::string> items = StringSplit(info, ":");
			if (items.size() == 2)
			{
				KeyValue keyValue;

				std::string keyString = items[0];
				keyString = keyString.erase(0, keyString.find_first_not_of(" "));

				std::string valueString = items[1];
				valueString = valueString.erase(0, valueString.find_first_not_of(" "));
				while (valueString.find("\n") != std::string::npos)
				{
					valueString = valueString.erase(valueString.find_first_of("\n"), 1);
				}
				while (valueString.find("\r") != std::string::npos)
				{
					valueString = valueString.erase(valueString.find_first_of("\r"), 1);
				}

				keyValue.key = keyString;
				keyValue.value = valueString;
				informations.push_back(keyValue);
			}
			else if (items.size() == 1)
			{
				items.push_back(temp);
			}
		}
		fclose(fileHandle);
	}
}

bool ComputerEnv::CreateCpuEnv()
{
#ifdef CYBERTRON_WIN
	static const int PROCESSORNUM = 100;
	HKEY hkey;
	int processorCount = 0;
	for (size_t index = 0; index < PROCESSORNUM; index++)
	{
		std::wstring keyName = UtilString::u2w("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" + std::to_string(index));
		if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName.c_str(), 0, KEY_READ, &hkey))
		{
			DWORD dwValue;
			DWORD dwSize = sizeof(DWORD);
			DWORD dwType = REG_DWORD;
			::RegQueryValueEx(hkey, UtilString::u2w("~MHz").c_str(), 0, &dwType, (LPBYTE)&dwValue, &dwSize);
			::RegCloseKey(hkey);

			mCpuEnvInfo.MHZ = std::to_string(dwValue);

			processorCount++;
		}
		else
		{
			break;
		}
	}
	mCpuEnvInfo.Processor = std::to_string(processorCount);
#else
	std::vector<KeyValue> informations;
	std::vector<std::string> items;
	ParseFile("/proc/cpuinfo", informations, items);

	for (int index = 0; index < informations.size(); index++)
	{
		KeyValue information = informations[index];
		if (information.key.find("CPU MHZ") != std::string::npos)
		{
			mCpuEnvInfo.MHZ = information.value;
			break;
		}
	}

	int processorCount = 0;
	for (int index = 0; index < informations.size(); index++)
	{
		KeyValue information = informations[index];
		if (information.key.find("PROCESSOR") != std::string::npos)
		{
			processorCount++;
		}
	}
	mCpuEnvInfo.Processor = std::to_string(processorCount);
#endif
	return true;
}

bool ComputerEnv::CreateMemoryEnv()
{
#ifdef CYBERTRON_WIN
	MEMORYSTATUS ms;
	::GlobalMemoryStatus(&ms);

	mMemoryEnvInfo.MemTotal = std::to_string(1.0f * atoll(std::to_string(ms.dwTotalPhys).c_str()) / (1024 * 1024 * 1024));
	mMemoryEnvInfo.MemFree = std::to_string(1.0f * atoll(std::to_string(ms.dwAvailPhys).c_str()) / (1024 * 1024 * 1024));
#else
	std::vector<KeyValue> informations;
	std::vector<std::string> items;
	ParseFile("/proc/meminfo", informations, items);

	for (int index = 0; index < informations.size(); index++)
	{
		KeyValue information = informations[index];
		if (information.key.find("MEMTOTAL") != std::string::npos)
		{
			mMemoryEnvInfo.MemTotal = std::to_string(1.0f * atoll(information.value.c_str()) / (1024 * 1024));
		}
		if (information.key.find("MEMFREE") != std::string::npos)
		{
			mMemoryEnvInfo.MemFree = std::to_string(1.0f * atoll(information.value.c_str()) / (1024 * 1024));
		}
	}
#endif
	return true;
}

bool ComputerEnv::CreateIPConfigEnv()
{
#ifdef CYBERTRON_WIN
	static const int ADAPTERNUM = 100;
	PIP_ADAPTER_INFO pIpAdapterInfo = new IP_ADAPTER_INFO[ADAPTERNUM];
	unsigned long stSize = sizeof(IP_ADAPTER_INFO) * ADAPTERNUM;
	int nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
	if (ERROR_BUFFER_OVERFLOW == nRel)
	{
		if (pIpAdapterInfo != NULL)
		{
			delete[] pIpAdapterInfo;
		}
		return false;
	}

	PIP_ADAPTER_INFO info = pIpAdapterInfo;
	while (info)
	{
		DeviceIPInfo deviceIPInfo;
		deviceIPInfo.name = info->Description;
		switch (info->Type)
		{
		case MIB_IF_TYPE_ETHERNET:
		{
			IP_ADDR_STRING *pIpAddrString = &(info->IpAddressList);
			deviceIPInfo.ip = pIpAddrString->IpAddress.String;
			deviceIPInfo.subnetMask = pIpAddrString->IpMask.String;
		}
		break;
		}
		char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };

		char mac[18] = { 0 };
		int maxIndex = 0;
		for (UINT addressIndex = 0; addressIndex < info->AddressLength; addressIndex++)
		{
			mac[maxIndex++] = hex[(info->Address[addressIndex] & 0xf0) >> 4];
			mac[maxIndex++] = hex[info->Address[addressIndex] & 0x0f];
			mac[maxIndex++] = '-';
		}
		if (maxIndex > 0) {
			mac[maxIndex - 1] = 0;
			deviceIPInfo.mac = mac;
		}
		else {
			deviceIPInfo.mac = "";
		}
		
		info = info->Next;
		mDeviceIPInfos.items.push_back(deviceIPInfo);

	}
	if (pIpAdapterInfo != NULL)
	{
		delete[] pIpAdapterInfo;
	}
#else
	int fd;
	int interfaceNum = 0;
	struct ifreq buf[16];
	struct ifconf ifc;
	struct ifreq ifrcopy;
	char mac[18] = { 0 };
	char ip[32] = { 0 };
	char broadAddress[32] = { 0 };
	char subnetMask[32] = { 0 };

	if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		close(fd);
		return -1;
	}

	ifc.ifc_len = sizeof(buf);
	ifc.ifc_buf = (caddr_t)buf;
	if (!ioctl(fd, SIOCGIFCONF, (char *)&ifc))
	{
		interfaceNum = ifc.ifc_len / sizeof(struct ifreq);
		while (interfaceNum-- > 0)
		{
			DeviceIPInfo deviceIPInfo;
			deviceIPInfo.name = buf[interfaceNum].ifr_name;

			ifrcopy = buf[interfaceNum];
			if (ioctl(fd, SIOCGIFFLAGS, &ifrcopy))
			{
				close(fd);
				return false;
			}

			if (!ioctl(fd, SIOCGIFHWADDR, (char *)(&buf[interfaceNum])))
			{
				memset(mac, 0, sizeof(mac));
				snprintf(mac, sizeof(mac), "%02x-%02x-%02x-%02x-%02x-%02x",
					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[0],
					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[1],
					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[2],

					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[3],
					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[4],
					(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[5]);


				deviceIPInfo.mac = mac;
			}
			else
			{
				close(fd);
				return false;
			}

			if (!ioctl(fd, SIOCGIFADDR, (char *)&buf[interfaceNum]))
			{
				snprintf(ip, sizeof(ip), "%s",
					(char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_addr))->sin_addr));

				deviceIPInfo.ip = ip;
			}
			else
			{
				close(fd);
				return false;
			}

			if (!ioctl(fd, SIOCGIFNETMASK, &buf[interfaceNum]))
			{
				snprintf(subnetMask, sizeof(subnetMask), "%s",
					(char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_netmask))->sin_addr));

				deviceIPInfo.subnetMask = subnetMask;
			}
			else
			{
				close(fd);
				return false;

			}

			mDeviceIPInfos.items.push_back(deviceIPInfo);
		}
	}
	else
	{
		close(fd);
		return false;
	}

	close(fd);
#endif
	return true;
}

bool ComputerEnv::CreateDiskEnv()
{
#ifdef CYBERTRON_WIN
	long long gSize = 1024 * 1024 * 1024;
	char rootPath[10] = { 0 }, driveType[21] = { 0 };
	UINT nType;
	for (char driver = 'A'; driver <= 'Z'; driver++)
	{
		sprintf(rootPath, "%c:\\", driver);
		nType = GetDriveType(UtilString::u2w(rootPath).c_str());
		if (nType != DRIVE_NO_ROOT_DIR && nType == DRIVE_FIXED)
		{
			DiskInfo diskInfo;
			long long diskSpace = 0;
			long long freeSpace = 0;
			DWORD dwSector;
			DWORD dwBytesInSec;
			DWORD dwCluster;
			DWORD dwFreeCluster;
			std::string driverString;
			driverString += driver;
			driverString += ":\\";

			BOOL result = GetDiskFreeSpace(UtilString::u2w(driverString).c_str(), &dwSector, &dwBytesInSec, &dwFreeCluster, &dwCluster);
			diskSpace = 0;
			freeSpace = 0;
			diskSpace = dwSector;
			diskSpace *= dwBytesInSec;
			diskSpace *= dwCluster;
			freeSpace = dwSector;
			freeSpace *= dwBytesInSec;
			freeSpace *= dwFreeCluster;

			diskInfo.name = driver;
			diskInfo.capacity = std::to_string(diskSpace / (1024 * 1024 * 1024));
			diskInfo.free = std::to_string(freeSpace / (1024 * 1024 * 1024));
			mDiskInfos.items.push_back(diskInfo);
		}
	}
#else
	char buffer[1024] = { 0 };
	std::string content = "";
	FILE* commondCache = popen("df -hl", "r");
	if (!commondCache)
	{
		return false;
	}
	while (!feof(commondCache))
	{
		if (fgets(buffer, sizeof(buffer), commondCache) != NULL)
		{
			content += buffer;
		}
	}
	pclose(commondCache);
	std::vector<std::string> lines = StringSplit(content, "\n");
	for (size_t index = 1; index < lines.size(); index++)
	{
		DiskInfo diskInfo;

		std::string line = lines[index];
		std::transform(line.begin(), line.end(), line.begin(), ::toupper);

		std::string lineNew;
		for (size_t pos = 0; pos < line.size(); pos++)
		{
			if (pos == 0)
			{
				lineNew += line[pos];
				continue;
			}

			if ((line[pos - 1] == ' ' || line[pos - 1] == '	') && (line[pos] == ' ' || line[pos] == '	'))
			{
				continue;
			}

			if (line[pos] == '	')
			{
				lineNew += ' ';
			}
			else
			{
				lineNew += line[pos];
			}
		}

		std::vector<std::string> items = StringSplit(lineNew, " ");
		if (items.size() >= 4)
		{
			diskInfo.name = items[0];
			diskInfo.capacity = items[1];
			diskInfo.free = items[3];
			mDiskInfos.items.push_back(diskInfo);
		}
	}
#endif
	return true;
}

#ifdef CYBERTRON_WIN
std::string WChar2String(LPCWSTR pwszSrc) {
	int len = WideCharToMultiByte(CP_ACP, 0, pwszSrc, -1, NULL, 0, NULL, NULL);
	if (len <= 0) {
		return std::string("");
	}
	char *pszDst = new char[len];
	if (nullptr == pszDst) {
		return std::string("");
	}
	WideCharToMultiByte(CP_ACP, 0, pwszSrc, -1, pszDst, len, NULL, NULL);
	pszDst[len - 1] = 0;
	std::string strTemp(pszDst);
	delete[]pszDst;
	return strTemp;
}
#endif
bool ComputerEnv::CreateDisplayEnv() {
#ifdef CYBERTRON_WIN
	DISPLAY_DEVICE displayDevice;
	ZeroMemory(&displayDevice, sizeof(displayDevice));
	displayDevice.cb = sizeof(displayDevice);
	DEVMODE devMode;
	ZeroMemory(&devMode, sizeof(devMode));
	devMode.dmSize = sizeof(devMode);

	DisplayInfo displayInfo;
	GraphicsInfo graphicsInfo;
	for (int i = 0; EnumDisplayDevices(NULL, i, &displayDevice, 0); ++i){
		memset(&displayInfo,0,sizeof(DisplayInfo));
		memset(&graphicsInfo, 0, sizeof(GraphicsInfo));
		if (EnumDisplaySettings(displayDevice.DeviceName, ENUM_CURRENT_SETTINGS, &devMode))
		{
			int index = i + 1;
			displayInfo.displayId = index;
			auto name = WChar2String(displayDevice.DeviceName);
			size_t i = name.find("DISPLAY", 0);
			name = name.substr(i);
			displayInfo.displayName = WChar2String(displayDevice.DeviceName);
			displayInfo.resolutionHeight = devMode.dmPelsHeight;
			displayInfo.resolutionWidth = devMode.dmPelsWidth;
			displayInfo.displayName = name;
			displayInfo.deviceID = WChar2String(displayDevice.DeviceID);
			if (displayDevice.StateFlags == DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) {
				displayInfo.isHomeScreen = true;
			}
			graphicsInfo.graphicsId = index;
			graphicsInfo.graphicsInterfacenNum = index;
			graphicsInfo.graphicsName = WChar2String(displayDevice.DeviceString);
			graphicsInfo.deviceID = WChar2String(displayDevice.DeviceID);
			mDisplayInfos.items.push_back(displayInfo);
			mGraphicsInfos.items.push_back(graphicsInfo);
		}
	}
#else
#endif
	return true;
}

void ComputerEnv::GetCpuEnvInfo(CpuEnvInfo& cpuEnvInfo)
{
	cpuEnvInfo = mCpuEnvInfo;
}

void ComputerEnv::GetMemoryEnvInfo(MemoryEnvInfo& memoryEnvInfo)
{
	memoryEnvInfo = mMemoryEnvInfo;
}

void ComputerEnv::GetIPConfigEnvInfo(DeviceIPInfos& deviceIPInfos)
{
	deviceIPInfos = mDeviceIPInfos;
}

void ComputerEnv::GetDiskInfo(DiskInfos& diskInfos)
{
	diskInfos = mDiskInfos;
}

void ComputerEnv::GetDisplayInfos(DisplayInfos& displayInfos)
{
	displayInfos = mDisplayInfos;
}

void ComputerEnv::GetGraphicsInfos(GraphicsInfos& graphicsInfos)
{
	graphicsInfos = mGraphicsInfos;
}


CYBERTRON_END